package com.jyniubi.gmail.index.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;

@Component
public class DistributedLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 加锁
     *
     * @param lockName
     * @param uuid
     * @param expire
     * @return
     */
    public Boolean tryLock(String lockName, String uuid, Integer expire) {
        // 如果锁不存在或锁没被占用 or 锁是自己的锁 重入次数加1 重置过期时间 返回true 否则返回false
        String script = "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
                "then " +
                "   redis.call('hincrby',KEYS[1],ARGV[1],1) " +
                "   redis.call('expire',KEYS[1],ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";
        Boolean flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString());
        // 如果没有获取到锁，等50ms后递归再次获取锁
        if (!flag) {
            try {
                Thread.sleep(50);
                this.tryLock(lockName, uuid, expire);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 自动续期
        renewExpiration(lockName,uuid,expire);
        return true;
    }

    /**
     * 解锁
     * @param lockName
     * @param uuid
     */
    public void unlock(String lockName, String uuid) {
        String script = "if redis.call('hexists',KEYS[1],ARGV[1])==0 " +
                "then " +
                "   return nil " +
                "elseif " +
                "   redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 " +
                "then " +
                "   return redis.call('del',KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
        Long flag = this.redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuid);
        if (flag == null) {
            throw new RuntimeException("您要释放的锁不存在！");
        }
    }

    /**
     * 自动给锁设置过期时间
     * @param lockName
     * @param uuid
     * @param expire
     */
    private void renewExpiration(String lockName, String uuid, Integer expire) {
        String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire',KEYS[1],ARGV[2]) " +
                "else " +
                "   return 0 " +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                Boolean flag = redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuid, expire.toString());
                if (flag){
                    //如果续期成功，开启下一轮续期
                    renewExpiration(lockName,uuid,expire);
                }
            }
        },expire * 1000/3);
    }
}
